import os
import chardet
import time
import docx
import re
from typing import List, Dict, Tuple
from openai import OpenAI
from collections import Counter


class SmartExamBot:
    def __init__(self, api_key: str, base_url: str, max_questions: int = 5):
        """初始化考试机器人"""
        self.api_key = api_key
        self.base_url = base_url
        self.client = OpenAI(
            api_key=api_key,
            base_url=base_url
        )
        self.max_follow_ups = 3
        self.question_count = 0
        self.total_score = 0
        self.max_possible_score = 0
        self.max_questions = max_questions
        self.current_follow_ups = 0
        self.question_scores = []  # 记录每题得分
        self.cannot_answer_patterns = re.compile(
            r'不会|不知道|不清楚|没学过|不懂|不了解|不会做|不知道怎么答|无法回答|我举不出来例子|分析不出来',
            re.IGNORECASE
        )
        self.irrelevant_patterns = re.compile(
            r'跑题|偏题|无关|答非所问|换个题|另一个|不相关|说点别的|其他内容|别的话题',
            re.IGNORECASE
        )
        self.image_patterns = re.compile(
            r'如图|图示|图片|图表|图[.\d]+|figure|graph|chart|diagram|illustration',
            re.IGNORECASE
        )
        self.knowledge_content = ""  # 存储知识库内容
        self.keywords = []  # 存储知识库关键词
        self.current_question = ""  # 存储当前题目内容
        self.question_dimensions = []  # 存储题目要求的维度
        # 扩展乱答检测的无关词汇库
        self.unrelated_topics = {'天气', '吃饭', '电影', '游戏', '睡觉', '时间', '日期',
                                 '音乐', '歌曲', '明星', '电影', '旅游', '购物', '美食',
                                 '体育', '运动', '小说', '书籍', '电视剧', '综艺'}

    def _extract_keywords(self, text: str) -> List[str]:
        """从知识库中提取关键词"""
        # 提取高频名词术语（中文2-6个字）
        words = re.findall(r'\b[\u4e00-\u9fa5]{2,6}\b', text)
        word_counts = Counter(words)

        # 过滤掉无意义的常见词
        stop_words = {'可以', '一个', '这个', '我们', '进行', '需要', '应该', '问题', '方法'}
        keywords = [word for word, count in word_counts.most_common(20)
                    if word not in stop_words and count > 1]

        return keywords[:10] if keywords else ["数据", "分析", "技术"]  # 默认关键词

    def _extract_dimensions(self, question: str) -> List[str]:
        """提取题目要求的分析维度"""
        # 从题目中提取类似"请从XX、XX和XX分析"的维度信息
        match = re.search(r'请从(.*?)分析', question)
        if match:
            dimensions_text = match.group(1)
            # 分割维度（支持"、"和"和"连接的维度）
            dimensions = [dim.strip() for dim in re.split(r'、|和|与', dimensions_text) if dim.strip()]
            return dimensions
        return []

    def _is_duplicating_question(self, question: str, answer: str) -> bool:
        """检测回答是否只是重复问题内容"""
        if not question or not answer:
            return False

        # 预处理问题和回答
        def preprocess(text):
            # 移除标点符号
            text = re.sub(r'[^\w\s]', '', text)
            # 移除常见引导词
            text = re.sub(r'请从|分析|简述|说明|论述|谈谈|如何|什么|为什么|是什么|有哪些', '', text,
                          flags=re.IGNORECASE)
            return text.strip()

        processed_question = preprocess(question)
        processed_answer = preprocess(answer)

        # 如果回答长度小于问题长度的50%，不可能是有效回答
        if len(processed_answer) < len(processed_question) * 0.5:
            return True

        # 计算回答与问题的重复率
        common_chars = 0
        min_len = min(len(processed_question), len(processed_answer))

        for i in range(min_len):
            if processed_question[i] == processed_answer[i]:
                common_chars += 1

        # 如果前30%的字符都相同，认为是重复问题
        if common_chars / min_len > 0.3:
            return True

        # 如果回答中包含超过70%的问题关键词
        question_words = set(re.findall(r'\b[\u4e00-\u9fa5a-zA-Z]{2,}\b', processed_question))
        answer_words = set(re.findall(r'\b[\u4e00-\u9fa5a-zA-Z]{2,}\b', processed_answer))

        if question_words and len(answer_words.intersection(question_words)) / len(question_words) > 0.7:
            return True

        return True if processed_answer in processed_question or processed_question in processed_answer else False

    def detect_encoding(self, file_path: str) -> str:
        """检测文件编码"""
        if file_path.lower().endswith('.docx'):
            return 'docx'
        try:
            with open(file_path, 'rb') as file:
                result = chardet.detect(file.read(10240))
                return result['encoding'] or 'utf-8'
        except Exception as e:
            raise Exception(f"编码检测失败: {str(e)}")

    def read_file_content(self, file_path: str) -> str:
        """读取文件内容，支持txt和docx格式"""
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")

        if not os.path.isfile(file_path):
            raise IsADirectoryError(f"{file_path}不是一个文件")

        try:
            encoding = self.detect_encoding(file_path)

            if encoding == 'docx':
                doc = docx.Document(file_path)
                full_text = []

                for para in doc.paragraphs:
                    full_text.append(para.text)

                for table in doc.tables:
                    for row in table.rows:
                        row_text = [cell.text for cell in row.cells]
                        full_text.append('\t'.join(row_text))

                return '\n'.join(full_text)

            encodings_to_try = [encoding, 'utf-8', 'gbk', 'latin-1']
            for enc in encodings_to_try:
                try:
                    with open(file_path, 'r', encoding=enc, errors='replace') as file:
                        return file.read()
                except UnicodeDecodeError:
                    continue
            raise Exception("无法识别文件编码")
        except Exception as e:
            raise Exception(f"文件读取失败: {str(e)}")

    def get_ai_response(self, messages: List[Dict], max_retries: int = 3) -> str:
        """获取AI响应，带重试机制"""
        retry_count = 0
        while retry_count < max_retries:
            try:
                response = self.client.chat.completions.create(
                    model="deepseek-v3",
                    messages=messages,
                    temperature=0.2,  # 降低随机性，使结果更稳定
                    timeout=30
                )
                return response.choices[0].message.content
            except Exception as e:
                retry_count += 1
                if retry_count >= max_retries:
                    raise Exception(f"API请求失败: {str(e)}")
                print(f"API调用失败，正在重试({retry_count}/{max_retries})...")
                time.sleep(2)

    def is_cannot_answer(self, user_input: str) -> bool:
        """判断用户输入是否表示'不会'"""
        user_input = user_input.strip()
        if not user_input or re.fullmatch(r'[\s,.!?;]+', user_input):
            return True

        # 检查是否包含明确表示不会的关键词
        return self.cannot_answer_patterns.search(user_input) is not None

    def is_irrelevant(self, user_answer: str) -> Tuple[bool, str]:
        """判断回答是否与题目和知识库无关（乱答判定）"""
        user_answer = user_answer.strip().lower()

        # 直接匹配无关表述
        if self.irrelevant_patterns.search(user_answer):
            return True, "回答明确表示与题目无关"

        # 提取题目关键词和维度关键词
        question_keywords = re.findall(r'\b[\u4e00-\u9fa5]{2,6}\b', self.current_question)
        all_keywords = question_keywords + self.keywords + self.question_dimensions

        # 检查是否包含任何相关关键词
        has_relevant_keywords = any(keyword in user_answer for keyword in all_keywords)

        if not has_relevant_keywords:
            # 检查是否包含无关主题词汇
            for topic in self.unrelated_topics:
                if topic in user_answer:
                    return True, f"回答涉及无关主题({topic})，视为乱答"

            # 长度过短且无相关关键词视为乱答
            if len(user_answer) < 5:
                return True, "回答过短且不涉及题目相关内容，视为乱答"

            # 完全不相关的内容
            return True, "回答与题目及知识库内容无关，视为乱答"

        return False, ""

    def start_exam(self, knowledge_content: str):
        """开始考试流程"""
        self.knowledge_content = knowledge_content
        self.keywords = self._extract_keywords(knowledge_content)

        print("\n" + "=" * 50)
        print("智能考试机器人".center(40))
        print("=" * 50)
        print("\n我将根据提供的知识内容对你进行考察。")
        print(f"考试共{self.max_questions}题，每道题会有最多{self.max_follow_ups}次追问。")
        print("输入'退出'可随时结束考试。")
        print("注意：可以输入多行内容，以空行结束输入\n")

        messages = [
            {"role": "system", "content": f"""
            你是一个公平的智能考试机器人，需要基于以下知识库内容出题和评分：
            {self.knowledge_content[:2000]}...

            工作模式：

            1. 题目生成模式：
            - 必须从知识库中提取具体概念出题
            - 题目必须包含至少1个关键词：{', '.join(self.keywords)}
            - 必须要求从2-3个维度回答
            - 示例："请从技术原理和应用场景两个维度分析知识库中提到的[关键词]"
            - 只返回题目，不要包含评分信息
            - 绝对不能包含任何图片、图示相关的内容

            2. 评分模式：
            - 评分标准（宽松版）：
              10分：全面覆盖所有维度，准确引用知识库内容，有具体实例
              8-9分：覆盖主要维度，正确引用知识库内容，有一定深度
              6-7分：部分覆盖维度，与知识库有一定关联，内容较简单
              4-5分：提到基本概念，与题目相关但深度不足，或关联较弱
              0分：未涉及题目核心内容或乱答
            - 必须按格式返回：分数|具体反馈|是否追问(是/否)
            - 反馈必须指出：
              * 回答覆盖了哪些维度
              * 与知识库内容的关联程度
              * 具体改进建议
            - 评分原则：只要与题目相关且有一定道理，就应给予合理分数
            """},
        ]

        while self.question_count < self.max_questions:
            self._handle_new_question(messages)
            while True:
                print("\n你: ", end="", flush=True)
                user_input_lines = []
                while True:
                    try:
                        line = input().strip()
                    except EOFError:
                        line = "退出"

                    if line.lower() in ['退出', 'q', 'quit']:
                        self._show_final_score()
                        return

                    if not line and user_input_lines:
                        user_input = '\n'.join(user_input_lines)
                        break

                    if line:
                        user_input_lines.append(line)
                    elif not user_input_lines:
                        print("请输入内容或命令：", end="", flush=True)
                        continue

                self._handle_user_answer(user_input, messages)
                if self.current_follow_ups == 0:
                    break

        self._show_final_score()

    def _handle_new_question(self, messages: List[Dict]):
        """题目生成逻辑"""
        self.question_count += 1
        self.current_follow_ups = 0
        max_retries = 5  # 增加重试次数，确保生成合格题目
        retry_count = 0

        while retry_count < max_retries:
            print(f"\n===== 第{self.question_count}/{self.max_questions}题 =====")

            prompt = f"""请基于以下知识库内容出一道主观题：
            {self.knowledge_content[:3000]}

            要求：
            1. 必须从以下关键词中选择至少1个：{', '.join(self.keywords)}
            2. 题目形式应为：
               "请从[维度1]、[维度2]和[维度3]分析知识库中提到的[关键词]..."
            3. 不要包含示例和解释
            4. 确保题目在知识库中有明确答案依据
            5. 绝对不能包含任何图片、图示、图表相关的内容
               例如：不能出现"如图所示"、"根据图X"、"figure"等表述"""

            messages.append({"role": "user", "content": prompt})
            question = self.get_ai_response(messages)

            # 验证题目质量
            if not any(keyword in question for keyword in self.keywords):
                print("⚠️ 题目未包含关键词，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            if len(re.findall(r'维度|角度|方面', question)) < 1:
                print("⚠️ 题目未明确要求多维度回答，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            # 检查是否包含图片相关表述
            if self.image_patterns.search(question):
                print("⚠️ 题目包含图片相关表述，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            # 清理格式
            question = question.split("|")[0].strip()
            question = re.sub(r'^\d+[\.\d]*\s*', '', question)

            if not question or len(question) < 10:
                print("⚠️ 题目过短，重新生成...")
                retry_count += 1
                messages.pop()
                continue

            # 保存当前题目和提取的维度
            self.current_question = question
            self.question_dimensions = self._extract_dimensions(question)
            messages.append({"role": "assistant", "content": question})
            print(f"考试机器人: {question}")
            self.max_possible_score += 10
            break
        else:
            print("⚠️ 多次尝试后仍无法获取有效题目，跳过本题")
            self.question_count -= 1
            return

    def _handle_user_answer(self, user_answer: str, messages: List[Dict]):
        """优化的评分逻辑：乱答0分，相关回答宽松给分"""
        # 先检查是否无法回答或乱答
        if self.is_cannot_answer(user_answer):
            print(f"\n用户回答: {user_answer}")
            print("\n检测到无法回答的表述，按规则得0分")
            self.total_score += 0
            self.question_scores.append(0)
            print(f"\n评分: 0.0/10")
            print(f"反馈: 本题需要按照题目要求的多个维度回答")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")
            messages.append({"role": "user", "content": user_answer})
            messages.append({"role": "assistant", "content": "0|检测到无法回答的表述|否"})
            return

        # 检查是否无关回答（乱答）
        is_irrelevant, reason = self.is_irrelevant(user_answer)
        if is_irrelevant:
            print(f"\n用户回答: {user_answer}")
            print(f"\n检测到无关回答: {reason}，按规则得0分")
            self.total_score += 0
            self.question_scores.append(0)
            print(f"\n评分: 0.0/10")
            print(f"反馈: 回答与题目或知识库内容无关，请围绕题目要求作答")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")
            messages.append({"role": "user", "content": user_answer})
            messages.append({"role": "assistant", "content": f"0|{reason}|否"})
            return

        # 检查是否重复问题
        if self._is_duplicating_question(self.current_question, user_answer):
            print(f"\n用户回答: {user_answer}")
            print("\n检测到回答只是重复或修改题目内容，按规则得0分")
            self.total_score += 0
            self.question_scores.append(0)
            print(f"\n评分: 0.0/10")
            print(f"反馈: 回答只是重复或修改题目内容，请提供具体分析和解释")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")
            messages.append({"role": "user", "content": user_answer})
            messages.append({"role": "assistant", "content": "0|回答只是重复或修改题目内容，未提供有效信息|否"})
            return

        # 检查维度匹配问题
        answer_dimensions = self._extract_dimensions(user_answer)
        if set(answer_dimensions) != set(self.question_dimensions):
            print(f"\n用户回答: {user_answer}")
            print("\n检测到回答维度与题目要求不匹配，按规则得0分")
            self.total_score += 0
            self.question_scores.append(0)
            print(f"\n评分: 0.0/10")
            print(f"反馈: 回答维度与题目要求不匹配，请按照题目要求的维度进行回答。题目要求维度：{', '.join(self.question_dimensions)}")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")
            messages.append({"role": "user", "content": user_answer})
            messages.append({"role": "assistant", "content": f"0|回答维度与题目要求不匹配，题目要求维度：{', '.join(self.question_dimensions)}|否"})
            return

        # 正常评分流程（宽松模式）
        messages.append({"role": "user", "content": user_answer})
        print(f"\n用户回答: {user_answer}")

        # 构建评分提示，强调与知识库比对
        evaluation = self.get_ai_response(messages + [
            {"role": "system", "content": f"""请按以下宽松标准评分：
            1. 知识库关键内容：{self.knowledge_content[:1000]}
            2. 题目要求维度：{', '.join(self.question_dimensions)}
            3. 评分标准：
               10分：全面覆盖所有维度，准确引用知识库内容，有具体实例
               8-9分：覆盖主要维度，正确引用知识库内容，有一定深度
               6-7分：部分覆盖维度，与知识库有一定关联，内容较简单
               4-5分：提到基本概念，与题目相关但深度不足，或关联较弱
               0分：未涉及题目核心内容或乱答
            4. 按以下格式返回：
               分数|具体反馈|是否追问(是/否)
            5. 反馈必须包含：
               - 回答覆盖了哪些维度
               - 与知识库内容的关联点（具体到关键词或概念）
               - 具体改进建议
            6. 评分原则：只要与题目相关且有一定道理，就应给予合理分数，不要过于严格
            7. 只输出题目，不要输出评分标准、参考答案、应用场景维度、伦理风险维度、评分原则等内容
            8. 不要输出评分标准、评分标准说明
            """},
        ])

        print(f"\n原始评分结果: {evaluation}")  # 添加调试信息

        # 严格的格式验证
        if not re.fullmatch(r'^\d+\|.+\|(是|否)$', evaluation.strip()):
            print("\n⚠️ 评分格式错误，使用默认评分")
            evaluation = "6|评分系统错误，按中等分数处理|否"  # 错误时给中等分而非低分

        try:
            score_part, feedback, need_follow_up = evaluation.strip().split('|')
            score = max(0, min(10, float(score_part)))

            # 确保有相关内容的回答不会得太低分
            question_keywords = re.findall(r'\b[\u4e00-\u9fa5]{2,6}\b', self.current_question)
            has_question_keywords = any(keyword in user_answer for keyword in question_keywords)
            has_knowledge_keywords = any(keyword in user_answer for keyword in self.keywords)

            if score < 4 and (has_question_keywords or has_knowledge_keywords):
                score = 4
                feedback = f"回答涉及题目关键词，调整分数。{feedback}"

            self.total_score += score
            self.question_scores.append(score)

            print(f"\n评分: {score:.1f}/10")
            print(f"反馈: {feedback}")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")

            if need_follow_up.strip().lower() == '是' and self.current_follow_ups < self.max_follow_ups:
                self.current_follow_ups += 1
                self._ask_follow_up(messages)
            else:
                print(f"\n本题结束，当前进度: {self.question_count}/{self.max_questions}题")
        except Exception as e:
            print(f"\n⚠️ 评分解析错误: {str(e)}")
            self.total_score += 6  # 错误时给中等分而非低分
            self.question_scores.append(6)
            print("本题按6分处理")

    def _ask_follow_up(self, messages: List[Dict]):
        """改进的追问逻辑"""
        follow_up_question = self.get_ai_response(messages + [
            {"role": "system", "content": f"""请基于以下内容提出追问：
            1. 知识库内容：{self.knowledge_content[:500]}
            2. 题目要求维度：{', '.join(self.question_dimensions)}
            3. 用户回答中最薄弱的维度
            4. 引导用户补充知识库中的具体内容
            5. 不要涉及任何图片、图示相关内容
            6. 只需返回问题内容"""}
        ])

        # 清理问题格式
        follow_up_question = follow_up_question.split("|")[0].strip()
        follow_up_question = re.sub(r'^\d+[\.\d]*\s*', '', follow_up_question)

        # 检查是否包含图片相关表述
        if self.image_patterns.search(follow_up_question):
            follow_up_question = re.sub(self.image_patterns, '', follow_up_question)
            follow_up_question = follow_up_question.strip()
            if not follow_up_question:
                follow_up_question = "请补充说明这个维度的具体内容"

        messages.append({
            "role": "assistant",
            "content": f"[追问 {self.current_follow_ups}/{self.max_follow_ups}] {follow_up_question}"
        })
        print(f"\n考试机器人 [追问 {self.current_follow_ups}/{self.max_follow_ups}]: {follow_up_question}")

    def _show_final_score(self):
        """显示最终得分"""
        print("\n" + "=" * 50)
        if self.question_count > 0:
            percentage = (self.total_score / self.max_possible_score) * 100 if self.max_possible_score > 0 else 0

            print(f"\n考试结束！共完成 {self.question_count} 道题")
            print("\n各题得分明细:")
            for i, score in enumerate(self.question_scores, 1):
                print(f"第{i}题: {score:.1f}/10")

            print(f"\n最终得分: {self.total_score:.1f}/{self.max_possible_score} ({percentage:.1f}%)")

            # 更详细的成绩分析
            if percentage >= 90:
                print("\n成绩卓越！全面掌握了知识库内容。")
            elif percentage >= 75:
                print("\n成绩优秀！对知识库内容有深入理解。")
            elif percentage >= 60:
                print("\n成绩良好！掌握了主要内容，细节有待加强。")
            elif percentage >= 40:
                print("\n成绩及格！需要加强知识库内容的学习。")
            else:
                print("\n未达标！建议系统复习知识库内容。")

            print("\n知识点掌握情况:")
            correct_counts = sum(1 for score in self.question_scores if score >= 6)
            print(f"- 掌握较好的知识点: {correct_counts}/{self.question_count}")
            print(f"- 需要加强的知识点: {self.question_count - correct_counts}/{self.question_count}")
        else:
            print("本次未完成任何题目。")
        print("=" * 50 + "\n")


if __name__ == "__main__":
    # 配置信息
    CONFIG = {
        "api_key": "sk-c501eb66620d47a9b1d1515e10598b47",
        "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "file_path": r"D:\桌面\竞赛\挑战杯+人工智能\第3章 数据获取.docx",
        "max_questions": 5
    }

    try:
        exam_bot = SmartExamBot(
            api_key=CONFIG["api_key"],
            base_url=CONFIG["base_url"],
            max_questions=CONFIG["max_questions"]
        )

        knowledge_content = exam_bot.read_file_content(CONFIG["file_path"])
        print(f"成功加载知识内容，长度: {len(knowledge_content)} 字符")
        print("提取的关键词:", ', '.join(exam_bot._extract_keywords(knowledge_content)))
        print("知识内容预览:", knowledge_content[:200] + "...")

        exam_bot.start_exam(knowledge_content)

    except Exception as e:
        print(f"程序运行出错: {str(e)}")
    finally:
        input("按回车键退出...")